<?php

namespace seecms\lib;

use seecms\See;
use seecms\lib\gif\Gif;
use seecms\SeeException;
use SplFileInfo;

class Image
{

    protected $info = [];

    protected $origin_file;

    /**
     * 图像资源对象
     *
     * @var resource
     */
    protected $im;

    /** @var  Gif */
    protected $gif;

    /* 缩略图相关常量定义 */
    const THUMB_SCALING = 1; //常量，标识缩略图等比例缩放类型
    const THUMB_FILLED = 2; //常量，标识缩略图缩放后填充类型
    const THUMB_CENTER = 3; //常量，标识缩略图居中裁剪类型
    const THUMB_NORTHWEST = 4; //常量，标识缩略图左上角裁剪类型
    const THUMB_SOUTHEAST = 5; //常量，标识缩略图右下角裁剪类型
    const THUMB_FIXED = 6; //常量，标识缩略图固定尺寸缩放类型
    /* 水印相关常量定义 */
    const WATER_NORTHWEST = 1; //常量，标识左上角水印
    const WATER_NORTH = 2; //常量，标识上居中水印
    const WATER_NORTHEAST = 3; //常量，标识右上角水印
    const WATER_WEST = 4; //常量，标识左居中水印
    const WATER_CENTER = 5; //常量，标识居中水印
    const WATER_EAST = 6; //常量，标识右居中水印
    const WATER_SOUTHWEST = 7; //常量，标识左下角水印
    const WATER_SOUTH = 8; //常量，标识下居中水印
    const WATER_SOUTHEAST = 9; //常量，标识右下角水印

    /**
     * @throws SeeException
     */
    public function __construct($origin_file)
    {
        //获取图像信息
        $info = @getimagesize($origin_file);

        //检测图像合法性
        if (false === $info || (IMAGETYPE_GIF === $info[2] && empty($info['bits']))) {
            throw new SeeException('Illegal image file');
        }

        //设置图像信息
        $this->info = [
            'width' => $info[0],
            'height' => $info[1],
            'type' => image_type_to_extension($info[2], false),
            'mime' => $info['mime'],
        ];

        $this->origin_file = $this->open($origin_file);

        //打开图像
        if ('gif' == $this->info['type']) {
            $this->gif = new Gif($this->origin_file->getPathname());
            $this->im = @imagecreatefromstring($this->gif->image());
        } else {
            $fun = "imagecreatefrom{$this->info['type']}";
            $this->im = @$fun($this->origin_file->getPathname());
        }

        if (empty($this->im)) {
            throw new SeeException('Failed to create image resources!');
        }
    }

    /**
     * @throws SeeException
     */
    public function open($origin_file): SplFileInfo
    {
        if (is_string($origin_file)) {
            $origin_file = new SplFileInfo($origin_file);
        }
        if (!$origin_file->isFile()) {
            throw new SeeException('image file not exist');
        }


        return $origin_file;
    }

    /**
     * 创建缩略图
     * @param string $size 尺寸
     * @param int $type 裁剪类型
     * @return  Image
     * @throws SeeException
     */
    public function thumb(string $size = '', int $type = 1): Image
    {
        $config = See::config()->upload;

        // 生成缩略图类型
        $type = $type ?: $config->thumbType();
        // 获取要生成的缩略图最大宽度和高度
        $size = $size ?: $config->thumbSize();
        if (empty($size)) {
            $size = [intval($this->info['width'] * 0.5), intval($this->info['height'] * 0.5)];
        }
        if (is_string($size)) {
            $size = explode(',', $size);
        }
        list($max_width, $max_height) = $size;

        //原图宽度和高度
        $w = $this->info['width'];
        $h = $this->info['height'];
        /* 计算缩略图生成的必要参数 */
        switch ($type) {
            /* 等比例缩放 */
            case self::THUMB_SCALING:
                //设置缩略图的坐标及宽度和高度
                $x = $y = 0;
                //原图尺寸小于缩略图尺寸则不进行缩略
                if ($w < $max_width && $h < $max_height) {
                    $this->crop($w, $h, $x, $y);
                    return $this;
                }
                //计算缩放比例
                $scale = min($max_width / $w, $max_height / $h);
                $max_width = $w * $scale;
                $max_height = $h * $scale;
                break;
            /* 居中裁剪 */
            case self::THUMB_CENTER:
                //计算缩放比例
                $scale = max($max_width / $w, $max_height / $h);
                //设置缩略图的坐标及宽度和高度
                $w = $max_width / $scale;
                $h = $max_height / $scale;
                $x = ($this->info['width'] - $w) / 2;
                $y = ($this->info['height'] - $h) / 2;
                break;
            /* 左上角裁剪 */
            case self::THUMB_NORTHWEST:
                //计算缩放比例
                $scale = max($max_width / $w, $max_height / $h);
                //设置缩略图的坐标及宽度和高度
                $x = $y = 0;
                $w = $max_width / $scale;
                $h = $max_height / $scale;
                break;
            /* 右下角裁剪 */
            case self::THUMB_SOUTHEAST:
                //计算缩放比例
                $scale = max($max_width / $w, $max_height / $h);
                //设置缩略图的坐标及宽度和高度
                $w = $max_width / $scale;
                $h = $max_height / $scale;
                $x = $this->info['width'] - $w;
                $y = $this->info['height'] - $h;
                break;
            /* 填充 */
            case self::THUMB_FILLED:
                //计算缩放比例
                if ($w < $max_width && $h < $max_height) {
                    $scale = 1;
                } else {
                    $scale = min($max_width / $w, $max_height / $h);
                }
                //设置缩略图的坐标及宽度和高度
                $neww = $w * $scale;
                $newh = $h * $scale;
                $x = $this->info['width'] - $w;
                $y = $this->info['height'] - $h;
                $posx = ($max_width - $w * $scale) / 2;
                $posy = ($max_height - $h * $scale) / 2;
                do {
                    //创建新图像
                    $img = imagecreatetruecolor($max_width, $max_height);
                    // 调整默认颜色
                    $color = imagecolorallocate($img, 255, 255, 255);
                    imagefill($img, 0, 0, $color);
                    //裁剪
                    imagecopyresampled($img, $this->im, $posx, $posy, $x, $y, $neww, $newh, $w, $h);
                    imagedestroy($this->im); //销毁原图
                    $this->im = $img;
                } while (!empty($this->gif) && $this->gifNext());
                $this->info['width'] = $max_width;
                $this->info['height'] = $max_height;
                return $this;
            /* 固定 */
            case self::THUMB_FIXED:
                $x = $y = 0;
                break;
            default:
                throw new SeeException('Unsupported thumbnail cropping type');
        }

        /* 裁剪图像 */
        $this->crop($w, $h, $x, $y, $max_width, $max_height);

        return $this;
    }

    /**
     * 切换到GIF的下一帧并保存当前帧
     */
    protected function gifNext()
    {
        ob_start();
        ob_implicit_flush(0);
        imagegif($this->im);
        $img = ob_get_clean();
        $this->gif->image($img);
        $next = $this->gif->nextImage();
        if ($next) {
            $this->im = imagecreatefromstring($next);
            return $next;
        } else {
            $this->im = imagecreatefromstring($this->gif->image());
            return false;
        }
    }

    /**
     * 裁剪图像
     *
     * @param integer|string $w 裁剪区域宽度
     * @param integer|string $h 裁剪区域高度
     * @param integer $x 裁剪区域x坐标
     * @param integer $y 裁剪区域y坐标
     * @param integer|string $width 图像保存宽度
     * @param integer|string $height 图像保存高度
     * @return Image
     */
    public function crop($w, $h, int $x = 0, int $y = 0, $width = null, $height = null): Image
    {
        //设置保存尺寸
        empty($width) && $width = $w;
        empty($height) && $height = $h;
        do {
            //创建新图像
            $img = imagecreatetruecolor($width, $height);
            // 调整默认颜色
            $color = imagecolorallocate($img, 255, 255, 255);
            imagefill($img, 0, 0, $color);
            //裁剪
            imagecopyresampled($img, $this->im, 0, 0, $x, $y, $width, $height, $w, $h);
            imagedestroy($this->im); //销毁原图
            //设置新图像
            $this->im = $img;
        } while (!empty($this->gif) && $this->gifNext());
        $this->info['width'] = $width;
        $this->info['height'] = $height;
        return $this;
    }

    /**
     * 图片添加水印
     * @param string $source
     * @param int $locate
     * @return Image
     * @throws SeeException
     */
    public function water(string $source = '', int $locate = self::WATER_SOUTHEAST): Image
    {
        if (empty($source)) {
            $images = See::config()->upload->waterImageList();
            $source = current($images);
        }

        if (!is_file($source)) {
            throw new SeeException('The watermark image does not exist');
        }
        //获取水印图像信息
        $info = getimagesize($source);
        if (false === $info || (IMAGETYPE_GIF === $info[2] && empty($info['bits']))) {
            throw new SeeException('The watermark image is illegal');
        }
        //创建水印图像资源
        $fun = 'imagecreatefrom' . image_type_to_extension($info[2], false);
        $water = $fun($source);
        //设定水印图像的混色模式
        imagealphablending($water, true);
        /* 设定水印位置 */
        switch ($locate) {
            /* 右下角水印 */
            case self::WATER_SOUTHEAST:
                $x = $this->info['width'] - $info[0];
                $y = $this->info['height'] - $info[1];
                break;
            /* 左下角水印 */
            case self::WATER_SOUTHWEST:
                $x = 0;
                $y = $this->info['height'] - $info[1];
                break;
            /* 左上角水印 */
            case self::WATER_NORTHWEST:
                $x = $y = 0;
                break;
            /* 右上角水印 */
            case self::WATER_NORTHEAST:
                $x = $this->info['width'] - $info[0];
                $y = 0;
                break;
            /* 居中水印 */
            case self::WATER_CENTER:
                $x = ($this->info['width'] - $info[0]) / 2;
                $y = ($this->info['height'] - $info[1]) / 2;
                break;
            /* 下居中水印 */
            case self::WATER_SOUTH:
                $x = ($this->info['width'] - $info[0]) / 2;
                $y = $this->info['height'] - $info[1];
                break;
            /* 右居中水印 */
            case self::WATER_EAST:
                $x = $this->info['width'] - $info[0];
                $y = ($this->info['height'] - $info[1]) / 2;
                break;
            /* 上居中水印 */
            case self::WATER_NORTH:
                $x = ($this->info['width'] - $info[0]) / 2;
                $y = 0;
                break;
            /* 左居中水印 */
            case self::WATER_WEST:
                $x = 0;
                $y = ($this->info['height'] - $info[1]) / 2;
                break;
            default:
                /* 自定义水印坐标 */
                if (is_array($locate)) {
                    list($x, $y) = $locate;
                } else {
                    throw new SeeException('Unsupported watermark location type');
                }
        }
        do {
            //添加水印
            $src = imagecreatetruecolor($info[0], $info[1]);
            // 调整默认颜色
            $color = imagecolorallocate($src, 255, 255, 255);
            imagefill($src, 0, 0, $color);
            imagecopy($src, $this->im, 0, 0, $x, $y, $info[0], $info[1]);
            imagecopy($src, $water, 0, 0, 0, 0, $info[0], $info[1]);
            imagecopymerge($this->im, $src, $x, $y, 0, 0, $info[0], $info[1], 100);
            //销毁零时图片资源
            imagedestroy($src);
        } while (!empty($this->gif) && $this->gifNext());
        //销毁水印资源
        imagedestroy($water);
        return $this;
    }

    /**
     * 文字水印
     *
     * @param string $text 添加的文字
     * @param string $font 字体路径
     * @param integer $size 字号
     * @param string $color 文字颜色，十六进制颜色值 #000000 黑色 #FF0000 红色 #ffffff 白色
     * @param int $locate 文字写入位置
     * @param integer|array $offset 文字相对当前位置的偏移量
     * @param integer $angle 文字倾斜角度
     * @return Image
     * @throws SeeException
     */
    public function text(string $text, string $font = '', int $size = 25, string $color = '#ffffff00', int $locate = self::WATER_SOUTHEAST, $offset = 0, int $angle = 30): Image
    {

        $config = See::config()->upload;

        // 不允许自定义水印文字
        if (!$config->waterTextOpen()) {
            $text = $config->waterTextDefault();
        }

        if (empty($font)) {
            $font = $config->waterTextFont();
        }

        if (!is_file($font)) {
            throw new SeeException("Non-existent font file：{$font}");
        }

        //获取文字信息
        $info = imagettfbbox($size, $angle, $font, $text);
        $minx = min($info[0], $info[2], $info[4], $info[6]);
        $maxx = max($info[0], $info[2], $info[4], $info[6]);
        $miny = min($info[1], $info[3], $info[5], $info[7]);
        $maxy = max($info[1], $info[3], $info[5], $info[7]);
        /* 计算文字初始坐标和尺寸 */
        $x = $minx;
        $y = abs($miny);
        $w = $maxx - $minx;
        $h = $maxy - $miny;
        /* 设定文字位置 */
        switch ($locate) {
            /* 右下角文字 */
            case self::WATER_SOUTHEAST:
                $x += $this->info['width'] - $w;
                $y += $this->info['height'] - $h;
                break;
            /* 左下角文字 */
            case self::WATER_SOUTHWEST:
                $y += $this->info['height'] - $h;
                break;
            /* 左上角文字 */
            case self::WATER_NORTHWEST:
                // 起始坐标即为左上角坐标，无需调整
                break;
            /* 右上角文字 */
            case self::WATER_NORTHEAST:
                $x += $this->info['width'] - $w;
                break;
            /* 居中文字 */
            case self::WATER_CENTER:
                $x += ($this->info['width'] - $w) / 2;
                $y += ($this->info['height'] - $h) / 2;
                break;
            /* 下居中文字 */
            case self::WATER_SOUTH:
                $x += ($this->info['width'] - $w) / 2;
                $y += $this->info['height'] - $h;
                break;
            /* 右居中文字 */
            case self::WATER_EAST:
                $x += $this->info['width'] - $w;
                $y += ($this->info['height'] - $h) / 2;
                break;
            /* 上居中文字 */
            case self::WATER_NORTH:
                $x += ($this->info['width'] - $w) / 2;
                break;
            /* 左居中文字 */
            case self::WATER_WEST:
                $y += ($this->info['height'] - $h) / 2;
                break;
            default:
                /* 自定义文字坐标 */
                if (is_array($locate)) {
                    list($posx, $posy) = $locate;
                    $x += $posx;
                    $y += $posy;
                } else {
                    throw new SeeException('Unsupported text position type');
                }
        }
        /* 设置偏移量 */
        if (is_array($offset)) {
            $offset = array_map('intval', $offset);
            list($ox, $oy) = $offset;
        } else {
            $offset = intval($offset);
            $ox = $oy = $offset;
        }
        /* 设置颜色 */
        if (is_string($color) && 0 === strpos($color, '#')) {
            $color = str_split(substr($color, 1), 2);
            // 十六进制转换为十进制
            $color = array_map('hexdec', $color);
            // 透明度  0 表示完全不透明，而 127 表示完全透明
            if (empty($color[3]) || $color[3] > 127) {
                $color[3] = 0;
            }
        } elseif (!is_array($color)) {
            throw new SeeException('Wrong color value');
        }

        do {
            /* 写入文字 */
            $col = imagecolorallocatealpha($this->im, $color[0], $color[1], $color[2], $color[3]);
            imagettftext($this->im, $size, $angle, $x + $ox, $y + $oy, $col, $font, $text);
        } while (!empty($this->gif) && $this->gifNext());
        return $this;
    }

    /**
     * 保存图像
     * @param string $filepath 图像保存路径名称
     * @param string|null $type 图像类型
     * @return bool
     */
    public function save(string $filepath, string $type = null): bool
    {
        //自动获取图像类型
        if (is_null($type)) {
            $type = $this->info['type'];
        } else {
            $type = strtolower($type);
        }
        //JPEG图像设置隔行扫描
        if ('jpeg' == $type || 'jpg' == $type) {
            $type = 'jpeg';
            imageinterlace($this->im, 1);
        }
        //保存图像
        if ('gif' == $type && !empty($this->gif)) {
            $this->gif->save($filepath);
        } else {
            $fun = "image{$type}";
            $fun($this->im, $filepath);
        }

        if (is_file($filepath)) {
            return true;
        }
        return false;
    }
}